<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Common;

use Srv\Libs\Frame\EnvData;
use Srv\Libs\Tools\Idna;

final class CommNet
{
    public const REQ_PORT_HEADER_X_CLIENT       = 'X-CLIENT-PORT';
    public const REQ_PORT_HEADER_REMOTE         = 'REMOTE_PORT';
    public const REQ_IP_HEADER_X_CLIENT         = 'X-CLIENT-IP';
    public const REQ_IP_HEADER_REMOTE           = 'REMOTE_ADDR';
    public const REQ_IP_HEADER_FORWARD_FOR      = 'X-FORWARDED-FOR';
    public const REQ_FORWARD_FOR_TYPE_DISABLE   = 0;
    public const REQ_FORWARD_FOR_TYPE_AFTER     = 1;
    public const REQ_FORWARD_FOR_TYPE_BEFORE    = 2;
    private static array $cacheHostIp           = [];       // 缓存HOST
    private static int $cacheExpire             = 60;       // 缓存有效期[秒]

    /**
     * @param string $ipStr
     * @return int
     * ipToLong
     */
    public static function ipToLong(string $ipStr):int
    {
        $ipLong         = ip2long($ipStr);
        return is_int($ipLong) ? $ipLong : 0;
    }

    /**
     * @param int $ipLong
     * @return string
     * longToIp
     */
    public static function longToIp(int $ipLong):string
    {
        $ipStr          = long2ip($ipLong);
        return is_string($ipStr) ? $ipStr : '';
    }

    /**
     * @param string $ipStrStart
     * @param string $ipStrEnd
     * @return array
     * ipToCidr
     */
    public static function ipToCidr(string $ipStrStart, string $ipStrEnd):array
    {
        return self::ipToCidrInt(self::ipToLong($ipStrStart), self::ipToLong($ipStrEnd));
    }

    /**
     * @param int $ipIntStart
     * @param int $ipIntEnd
     * @return array
     * ipToCidrInt
     */
    public static function ipToCidrInt(int $ipIntStart, int $ipIntEnd):array
    {
        $bit        = 32;
        $start      = $ipIntStart;
        $end        = $ipIntEnd;
        $cidrList   = [];
        while($end >= $start){
            $maxSize    = $bit;
            while($maxSize > 0 && ($start == ($start & (4294967296 - pow(2, 33 - $maxSize))))) $maxSize--;
            $maxDiff    = floor($bit - floor(log($end - $start + 1)/log(2)));
            $maxSize    = max($maxSize, $maxDiff);
            $cidrList[] = self::longToIp($start).'/'.$maxSize;
            $start      += pow(2, ($bit - $maxSize));
        }
        return $cidrList;
    }

    /**
     * @param string $cidrIpStr
     * @return int[]
     * cidrToIpInt
     */
    public static function cidrToIpInt(string $cidrIpStr):array
    {
        $ipIntRange     = [0, 0];
        if(preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)/', $cidrIpStr, $match)){
            $startInt   = self::ipToLong(min(max(intval($match[1]), 0), 255).'.'.min(max(intval($match[2]), 0), 255).'.'.min(max(intval($match[3]), 0), 255).'.'.min(max(intval($match[4]), 0), 255));
            $maskInt    = min(max(intval($match[5]), 0), 32);
            if($maskInt === 32){
                $ipIntRange[0]  = $startInt;
                $ipIntRange[1]  = $startInt;
            }else{
                $ipIntRange[0]  = $startInt & (4294967296 - pow(2, 32 - $maskInt));
                $ipIntRange[1]  = $ipIntRange[0] | (pow(2, 32 - $maskInt) -1);
            }
        }
        return $ipIntRange;
    }

    /**
     * @param string $ipRangeStr
     * @return array|int[]
     * getIpIntRange
     */
    public static function getIpIntRange(string $ipRangeStr):array
    {
        $ipIntRange         = [0, 0];
        if(CommValid::isIp($ipRangeStr)){
            $ipIntRange[0]  = $ipIntRange[1] = self::ipToLong($ipRangeStr);
        }else if(preg_match("/(.*)-(\d+)/", $ipRangeStr, $match)){
            $ipStrStart     = trim($match[1]);
            if(CommValid::isIp($ipStrStart)) $ipIntRange = [self::ipToLong($ipStrStart), self::ipToLong(substr($ipStrStart, 0, strrpos($ipStrStart, '.')+1).$match[2])];
        }else if(preg_match("/(.*)\/(\d+)/", $ipRangeStr, $match)){
            $ipIntRange     = self::cidrToIpInt($ipRangeStr);
        }
        return $ipIntRange;
    }

    /**
     * @param int $ipLong
     * @param string $cidrIpStr
     * @return bool
     * ipInCidr
     */
    public static function ipInCidr(int $ipLong, string $cidrIpStr):bool
    {
        if($ipLong < 1 || strlen($cidrIpStr) < 9) return false;
        $ipIntRange     = self::cidrToIpInt($cidrIpStr);
        if($ipIntRange[0] < 1 || $ipIntRange[1] < 1) return false;
        return $ipLong >= $ipIntRange[0] && $ipLong <= $ipIntRange[1];
    }

    /**
     * @param int $ipCheck
     * @param string $ipStr
     * @return bool
     * isInIpStrAll
     */
    public static function isInIpStrAll(int $ipCheck, string $ipStr):bool
    {
        if($ipStr === '*') return true;
        if(preg_match("/(.*)-(\d+)/", $ipStr, $match)){
            $ipStrStart         = trim($match[1]);
            if(CommValid::isIp($ipStrStart)){
                $ipIntMin       = self::ipToLong($ipStrStart);
                $ipIntMax       = self::ipToLong(substr($ipStrStart, 0, strrpos($ipStrStart, '.')+1).$match[2]);
                if($ipCheck >= $ipIntMin && $ipCheck <= $ipIntMax) return true;
            }
        }else if(preg_match("/(.*)\/(\d+)/", $ipStr, $match)){
            return self::ipInCidr($ipCheck, $ipStr);
        }else if(CommValid::isIp($ipStr)){
            return $ipCheck === self::ipToLong($ipStr);
        }
        return false;
    }

    /**
     * @param string $hostIp
     * @return string
     * getHostNameByIp
     */
    public static function getHostNameByIp(string $hostIp):string
    {
        $hostName                   = '';
        $hostIp                     = trim($hostIp);
        if(!CommValid::isIp($hostIp)) return $hostName;
        $currTime                   = CommTime::getTimeStamp();
        if(count(self::$cacheHostIp) > 0){
            $expireTime             = $currTime - self::$cacheExpire;
            foreach(self::$cacheHostIp as $key => $val){
                if($val['time'] > $expireTime){
                    if($val['ip'] === $hostIp && $val['host'] !== ''){
                        $hostName   = $val['host'];
                        break;
                    }
                }else{
                    unset(self::$cacheHostIp[$key]);
                }
            }
        }
        if($hostName === ''){
            $hostNameVal                = gethostbyaddr($hostIp);
            if(is_string($hostNameVal) && $hostNameVal !== '' && $hostNameVal !== $hostIp){
                $hostName               = $hostNameVal;
                self::$cacheHostIp[]    = ['ip' => $hostIp, 'host' => $hostName, 'time' => $currTime];
            }
        }
        return $hostName;
    }

    /**
     * @param string $hostName
     * @return string
     * getHostIpByName
     */
    public static function getHostIpByName(string $hostName):string
    {
        $hostIp                 = '';
        $hostName               = strtolower(trim($hostName));
        if(strlen($hostName) < 1) return $hostIp;
        $currTime               = CommTime::getTimeStamp();
        if(count(self::$cacheHostIp) > 0){
            $expireTime         = $currTime - self::$cacheExpire;
            foreach(self::$cacheHostIp as $key => $val){
                if($val['time'] > $expireTime){
                    if($val['host'] === $hostName && $val['ip'] !== ''){
                        $hostIp = $val['ip'];
                        break;
                    }
                }else{
                    unset(self::$cacheHostIp[$key]);
                }
            }
        }
        if($hostIp === ''){
            $hostIpVal                  = gethostbyname($hostName);
            if($hostIpVal !== '' && $hostIpVal !== $hostName){
                $hostIp                 = $hostIpVal;
                self::$cacheHostIp[]    = ['ip' => $hostIp, 'host' => $hostName, 'time' => $currTime];
            }
        }
        return $hostIp;
    }

    /**
     * @param array $beforeHeadKeyList
     * @return int
     * getReqPort
     */
    public static function getReqPort(array $beforeHeadKeyList = []):int
    {
        $headKeyList                = [self::REQ_PORT_HEADER_X_CLIENT, self::REQ_PORT_HEADER_REMOTE];
        if(count($beforeHeadKeyList) > 0){
            $beforeHeadKeyRes       = [];
            foreach ($beforeHeadKeyList as $beforeHeadKey){
                if(!is_string($beforeHeadKey)) continue;
                $beforeHeadKey      = strtoupper(trim($beforeHeadKey));
                if(strlen($beforeHeadKey) < 1) continue;
                $beforeHeadKeyRes[] = $beforeHeadKey;
            }
            if(count($beforeHeadKeyRes) > 0) $headKeyList = array_unique(array_merge($beforeHeadKeyRes, $headKeyList));
        }
        foreach($headKeyList as $headKey){
            if($headKey === self::REQ_PORT_HEADER_REMOTE){
                $headVal    = EnvData::getDataByServerItem($headKey);
            }else{
                $headVal    = EnvData::getDataByHeaderItem($headKey);
            }
            if(is_numeric($headVal)){
                $portVal    = intval($headVal);
                if($portVal > 0 && $portVal < 65535) return $portVal;
            }
        }
        return 0;
    }

    /**
     * @param array $beforeHeadKeyList
     * @param int $forwardForType
     * @param bool $ipv6Allow
     * @return string
     * getReqIp
     */
    public static function getReqIp(array $beforeHeadKeyList = [], int $forwardForType = self::REQ_FORWARD_FOR_TYPE_BEFORE, bool $ipv6Allow = false):string
    {
        $headKeyList                = [self::REQ_IP_HEADER_X_CLIENT, self::REQ_IP_HEADER_REMOTE];
        if($forwardForType === self::REQ_FORWARD_FOR_TYPE_BEFORE){
            array_splice($headKeyList, 0, 0, self::REQ_IP_HEADER_FORWARD_FOR);
        }else if($forwardForType === self::REQ_FORWARD_FOR_TYPE_AFTER){
            array_splice($headKeyList, 1, 0, self::REQ_IP_HEADER_FORWARD_FOR);
        }
        if(count($beforeHeadKeyList) > 0){
            $beforeHeadKeyRes       = [];
            foreach ($beforeHeadKeyList as $beforeHeadKey){
                if(!is_string($beforeHeadKey)) continue;
                $beforeHeadKey      = strtoupper(trim($beforeHeadKey));
                if(strlen($beforeHeadKey) < 1) continue;
                $beforeHeadKeyRes[] = $beforeHeadKey;
            }
            if(count($beforeHeadKeyRes) > 0) $headKeyList = array_unique(array_merge($beforeHeadKeyRes, $headKeyList));
        }
        foreach($headKeyList as $headKey){
            $headVal            = $headKey === self::REQ_IP_HEADER_REMOTE ? EnvData::getDataByServerItem($headKey) : EnvData::getDataByHeaderItem($headKey);
            if(!is_string($headVal) || strlen($headVal) < 2) continue;
            foreach(explode(',', $headVal) as $headValItem){
                $headValItem    = trim($headValItem);
                if(strlen($headValItem) < 2) continue;
                if(CommValid::isIp($headValItem)) return $headValItem;
                $ipv6FillStr    = CommNet::ipV6Fill($headValItem);
                if(strlen($ipv6FillStr) === 39){
                    if(preg_match('/^0{4}:0{4}:0{4}:0{4}:0{4}:0{4}:0{4}:[\dA-Fa-f]{4}$/', $ipv6FillStr)){   // ::1
                        if($ipv6Allow) return $ipv6FillStr;
                    }else if(preg_match('/^0{4}:0{4}:0{4}:0{4}:0{4}:(f{4}|F{4}|0{4}):([\dA-Fa-f]{4}):([\dA-Fa-f]{4})$/', $ipv6FillStr, $match)){
                        return CommString::hexToDec(substr($match[2], 0, 2)).'.'.CommString::hexToDec(substr($match[2], 2, 2)).'.'.CommString::hexToDec(substr($match[3], 0, 2)).'.'.CommString::hexToDec(substr($match[3], 2, 2));
                    }
                    if($ipv6Allow) return $ipv6FillStr;
                }
            }
        }
        return '';
    }

    /**
     * @param array $beforeHeadKeyList
     * @param int $forwardForType
     * @param bool $ipv6Allow
     * @param string $ipStr
     * @param array $ipv6Num8List
     * @return int
     * getReqIpInt
     */
    public static function getReqIpInt(array $beforeHeadKeyList = [], int $forwardForType = self::REQ_FORWARD_FOR_TYPE_BEFORE, bool $ipv6Allow = false, string &$ipStr = '', array &$ipv6Num8List = []):int
    {
        $ipStr              = self::getReqIp($beforeHeadKeyList, $forwardForType, $ipv6Allow);
        return self::genReqIpInt($ipStr, $ipv6Allow, $ipv6Num8List);
    }

    /**
     * @param string $ipStr
     * @param bool $ipv6Allow
     * @param array $ipv6Num8List
     * @return int
     * genReqIpInt
     */
    public static function genReqIpInt(string $ipStr, bool $ipv6Allow = false, array &$ipv6Num8List = []):int
    {
        if(strlen($ipStr) < 2) return 0;
        if(CommValid::isIp($ipStr)) return self::ipToLong($ipStr);
        if($ipv6Allow){
            $ipv6FillStr    = self::ipv6Fill($ipStr);
            $ipv6Num8List   = self::ipv6ToNum8($ipv6FillStr);
            if(count($ipv6Num8List) !== 8) return 0;
            return CommString::crc64DecInt($ipv6FillStr);
        }
        return 0;
    }

    /**
     * @param array $beforeHeadKeyList
     * @param int $forwardForType
     * @param bool $ipv6Allow
     * @param string $ipStr
     * @param array $ipv6Num8List
     * @return string
     * getReqIpNumStr
     */
    public static function getReqIpNumStr(array $beforeHeadKeyList = [], int $forwardForType = self::REQ_FORWARD_FOR_TYPE_BEFORE, bool $ipv6Allow = false, string &$ipStr = '', array &$ipv6Num8List = []):string
    {
        $ipStr              = self::getReqIp($beforeHeadKeyList, $forwardForType, $ipv6Allow);
        return self::genIpNumStr($ipStr, $ipv6Allow, $ipv6Num8List);
    }

    /**
     * @param string $ipStr
     * @param bool $ipv6Allow
     * @param array $ipv6Num8List
     * @return string
     * genIpNumStr
     */
    public static function genIpNumStr(string $ipStr, bool $ipv6Allow = false, array &$ipv6Num8List = []):string
    {
        if(strlen($ipStr) < 2) return '0';
        if(CommValid::isIp($ipStr)) return strval(self::ipToLong($ipStr));
        if($ipv6Allow){
            $ipv6FillStr    = self::ipv6Fill($ipStr);
            $ipv6Num8List   = self::ipv6ToNum8($ipv6FillStr);
            if(count($ipv6Num8List) !== 8) return '0';
            $ipIntStr       = CommString::crc64DecStr($ipv6FillStr);
            if(bccomp($ipIntStr, '4294967295') !== 1) $ipIntStr = bcadd($ipIntStr, '4294967296'); // 确保超过IPv4的最大数
            return $ipIntStr;
        }
        return '0';
    }

    /**
     * @param string $ipv6Str
     * @return array
     * ipv6ToNum8
     */
    public static function ipv6ToNum8(string $ipv6Str):array
    {
        $ipv6FillStr    = self::ipv6Fill($ipv6Str);
        if(strlen($ipv6FillStr) < 1) return [];
        $ipv6FillList   = explode(':', $ipv6FillStr);
        foreach ($ipv6FillList as &$ipv6FillItem) $ipv6FillItem = intval(CommString::hexToDec($ipv6FillItem));
        return $ipv6FillList;
    }

    /**
     * @param array $ipv6Num8List
     * @param bool $isFill
     * @return string
     * num8ToIpv6
     */
    public static function num8ToIpv6(array $ipv6Num8List, bool $isFill = false):string
    {
        if(count($ipv6Num8List) !== 8) return '';
        foreach ($ipv6Num8List as &$ipv6Num8Item){
            if(is_string($ipv6Num8Item)){
                if(strlen($ipv6Num8Item) < 1 || !is_numeric($ipv6Num8Item)) return '';
                $ipv6Num8Item   = intval($ipv6Num8Item);
                if($ipv6Num8Item < 0 || $ipv6Num8Item > 65535) return '';
            }
            if(!is_int($ipv6Num8Item) || $ipv6Num8Item < 0 || $ipv6Num8Item > 65535) return '';
            $ipv6Num8Item       = CommString::decToHex(strval($ipv6Num8Item));
        }
        $ipv6Str                = implode(':', $ipv6Num8List);
        return $isFill ? self::ipv6Fill($ipv6Str) : self::ipv6Lite($ipv6Str);
    }

    /**
     * @param string $ipv6Str
     * @return array
     * ipv6ToStrNum
     */
    public static function ipv6ToStrNum(string $ipv6Str):array
    {
        $ipv6FillStr    = self::ipv6Fill($ipv6Str);
        if(strlen($ipv6FillStr) < 1) return [];
        $ipv6FillList   = explode(':', $ipv6FillStr);
        return CommString::hexToDecArray(implode('', [...array_slice($ipv6FillList, 0, 4), ...array_slice($ipv6FillList, 4, 4)]));
    }

    /**
     * @param array $ipv6StrNumList
     * @param bool $isFill
     * @return string
     * strNumToIpv6
     */
    public static function strNumToIpv6(array $ipv6StrNumList, bool $isFill = false):string
    {
        if(count($ipv6StrNumList) !== 2 || !isset($ipv6StrNumList[0]) || !isset($ipv6StrNumList[1])) return '';
        $ipv6HexStr     = CommString::decToHexArray($ipv6StrNumList);
        if(strlen($ipv6HexStr) !== 32) return '';
        $ipv6Str        = substr(preg_replace('/([a-f\d]{4})/', '$1:', $ipv6HexStr), 0, -1);
        return $isFill ? $ipv6Str : self::ipv6Lite($ipv6Str);
    }

    /**
     * @param string $ipv6Str
     * @return string
     * ipv6Fill
     */
    public static function ipv6Fill(string $ipv6Str):string
    {
        if(CommValid::isIpv6Fill($ipv6Str)) return strtolower($ipv6Str);
        if(!CommValid::isIpv6($ipv6Str)) return '';
        $ipv6Binary     = inet_pton($ipv6Str);
        if(!is_string($ipv6Binary)) return '';
        $ipv6HexData    = unpack('H*hex', $ipv6Binary);
        return substr(preg_replace('/([a-f\d]{4})/', '$1:', $ipv6HexData['hex']??''), 0, -1);
    }

    /**
     * @param string $ipv6FillStr
     * @return string
     * ipv6LiteByFill
     */
    public static function ipv6LiteByFill(string $ipv6FillStr):string
    {
        if(!CommValid::isIpv6Fill($ipv6FillStr)) return '';
        $ipv6FillList   = explode(':', $ipv6FillStr);
        $numList        = [];
        $indexStart     = -1;
        $indexNum       = 0;
        foreach ($ipv6FillList as $ipv6FillKey => &$ipv6FillItem){
            $ipv6FillItem       = ltrim($ipv6FillItem, '0');
            if(strlen($ipv6FillItem) < 1){
                $ipv6FillItem   = '0';  // 压缩每段前置0
                if($indexNum > 0){
                    ++$indexNum;
                }else{
                    $indexNum   = 1;
                    $indexStart = $ipv6FillKey;
                }
            }else{
                if($indexNum > 0) $numList[$indexStart] = $indexNum;
                $indexStart     = -1;
                $indexNum       = 0;
            }
        }
        if($indexNum > 0) $numList[$indexStart] = $indexNum;
        if(count($numList) > 0){
            arsort($numList);   // 压缩一次最长的连续0
            foreach ($numList as $indexStart => $indexNum){
                if($indexStart === 0 && $indexNum === 8){
                    $ipv6FillList   = ['', '', ''];
                }else{
                    array_splice($ipv6FillList, $indexStart, $indexNum, ($indexStart === 0 || ($indexStart+$indexNum) === 8) ? ['', ''] : ['']);
                }
                break;
            }
        }
        return implode(':', $ipv6FillList);
    }

    /**
     * @param string $ipv6Str
     * @return string
     * ipv6Lite
     */
    public static function ipv6Lite(string $ipv6Str):string
    {
        return self::ipv6LiteByFill(self::ipv6Fill($ipv6Str));
    }

    /**
     * @param string $ipStr
     * @param bool $isFill
     * @param bool $isZero
     * @return string
     * ipToIpv6
     */
    public static function ipToIpv6(string $ipStr, bool $isFill = false, bool $isZero = false):string
    {
        if(!CommValid::isIp($ipStr)) return '';
        $ipv6Str        = '::'.($isZero ? '' : 'ffff:').$ipStr;
        return $isFill ? self::ipv6Fill($ipv6Str) : self::ipv6Lite($ipv6Str);
    }

    /**
     * @param int $ipLong
     * @param bool $isFill
     * @return string
     * ipLongToIpv6
     */
    public static function ipLongToIpv6(int $ipLong, bool $isFill = false):string
    {
        return self::ipToIpv6(self::longToIp($ipLong), $isFill);
    }

    /**
     * @param string $url
     * @param array $param
     * @return array
     * getCurlInfo
     */
    public static function getCurlInfo(string $url, array $param = []):array
    {
        !defined('CURLOPT_PROXYHEADER') && define('CURLOPT_PROXYHEADER', 10228);            // 条件cURL>=7.37.0
        !defined('CURLOPT_CONNECT_TO') && define('CURLOPT_CONNECT_TO', 10243);              // 条件cURL>=7.49.0
        $options    = [
            CURLOPT_URL                 => $url,                    // 请求地址
            CURLOPT_RETURNTRANSFER      => true,                    // 返回原始RAW内容
            CURLOPT_FOLLOWLOCATION      => false,                   // 不允许重定向
            CURLOPT_MAXREDIRS           => 20,                      // 允许重定向最大次数
            CURLOPT_HEADER              => true,                    // 输出头信息
            CURLOPT_NOBODY              => false,                   // 不输出正文信息
            CURLOPT_POST                => false,                   // 非POST请求
            CURLOPT_CUSTOMREQUEST       => 'GET',                   // 请求头自定义方法名[GET,POST,HEAD,PUT,DELETE]
            CURLOPT_SSL_VERIFYPEER      => false,                   // 禁止验证SSL
            CURLOPT_CONNECTTIMEOUT      => 3,                       // 连接时等待时长[秒,0-无限等待]
            CURLOPT_TIMEOUT             => 60,                      // 允许执行的时长[秒]
            CURLOPT_DNS_USE_GLOBAL_CACHE=> true,                    // 全局DNS缓存
            CURLOPT_DNS_CACHE_TIMEOUT   => 60,                      // DNS缓存时长[秒],默认:120秒
            CURLOPT_HTTP_VERSION        => CURL_HTTP_VERSION_1_1,   // 版本 CURL_HTTP_VERSION_1_0
            CURLOPT_IPRESOLVE           => CURL_IPRESOLVE_WHATEVER, // 请求IP类别 [CURL_IPRESOLVE_V4, CURL_IPRESOLVE_V6]
            CURLOPT_PROXYTYPE           => CURLPROXY_HTTP,          // 代理类别[CURLPROXY_SOCKS5_HOSTNAME]
            CURLOPT_PROXY               => '',                      // 代理服务主机
            CURLOPT_PROXYPORT           => 8889,                    // 代理服务端口
            CURLOPT_PROXYAUTH           => CURLAUTH_BASIC,          // 代理认证
            CURLOPT_PROXYUSERPWD        => '',                      // 代理认证的用户和密码[username]:[password]
            CURLOPT_PROXYHEADER         => [],                      // 代理请求头多行内容['Content-type: application/json; charset=UTF-8']
            CURLOPT_HTTPAUTH            => CURLAUTH_BASIC,          // 请求认证
            CURLOPT_USERPWD             => '',                      // 请求认证的用户和密码[username]:[password]
//            CURLOPT_ENCODING            => '',                      // 请求头Accept-Encoding值[identity,deflate,gzip]空则全部支持的
//            CURLOPT_USERAGENT           => '',                      // 请求头User-Agent值
            CURLOPT_REFERER             => '',                      // 请求头Referer值
            CURLOPT_COOKIE              => '',                      // 请求头Cookie内容[name=value; name2=value2][分号后有1个空格]
            CURLOPT_HTTPHEADER          => [],                      // 请求头多行内容['Content-type: application/json; charset=UTF-8']
            CURLOPT_CONNECT_TO          => [],                      // 请求连接指定主机和端口['HOST:PORT:CONNECT-TO-HOST:CONNECT-TO-PORT']
            CURLOPT_AUTOREFERER         => true,                    // 重定向时自动设置header中的Referer
        ];
        $isProxy                        = false;
        $br                             = "\r\n";
        $proxyHttpIp                    = '';                       // 代理HTTP-IP
        $proxyHttpPort                  = 8889;                     // 代理HTTP端口
        $proxyHttpUser                  = '';                       // 代理HTTP用户名
        $proxyHttpPass                  = '';                       // 代理HTTP密码
        $proxySocks5Ip                  = '';                       // 代理SOCKS5-IP
        $proxySocks5Port                = 1089;                     // 代理SOCKS5端口
        $proxySocks5User                = '';                       // 代理SOCKS5用户名
        $proxySocks5Pass                = '';                       // 代理SOCKS5密码
        $reqHttpUser                    = '';                       // 请求HTTP用户名
        $reqHttpPass                    = '';                       // 请求HTTP密码
        $reqHttpAuthType                = '';                       // 请求HTTP认证类型
        $reqHost                        = '';                       // 请求主机
        $reqPort                        = 0;                        // 请求端口
        // 设置默认值
        $options[CURLOPT_ENCODING]      = 'gzip, deflate';
        $options[CURLOPT_USERAGENT]     = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36';
        if(count($param) > 0) foreach($param as $key => $val){
            switch($key){
                case 'VERSION' : {              $options[CURLOPT_HTTP_VERSION] = $val === '1.0' ? CURL_HTTP_VERSION_1_0 : CURL_HTTP_VERSION_1_1; break; }
                case 'IS_NOBODY' : {            $options[CURLOPT_NOBODY] = !!$val; break; }
                case 'IS_FOLLOWLOCATION' : {    $options[CURLOPT_FOLLOWLOCATION] = !!$val; break; }
                case 'FOLLOWLOCATION_NUM' : {   $options[CURLOPT_FOLLOWLOCATION] = min(max(intval($val), 0), 999); break; }
                case 'TIMEOUT' : {              $options[CURLOPT_TIMEOUT] = min(max(intval($val), 0), 9999); break; }
                case 'METHOD' : {               $options[CURLOPT_CUSTOMREQUEST] = strtoupper($val); break; }
                case 'ENCODING' : {             $options[CURLOPT_ENCODING] = $val; break; }
                case 'USERAGENT' : {            $options[CURLOPT_USERAGENT] = $val; break; }
                case 'USER' : {                 $reqHttpUser        = trim($val); break; }
                case 'PASS' : {                 $reqHttpPass        = trim($val); break; }
                case 'AUTH_TYPE' : {            $reqHttpAuthType    = trim($val); break; }
                case 'PROXY_HTTP_IP' : {        $proxyHttpIp        = trim($val); break; }
                case 'PROXY_HTTP_PORT' : {      $proxyHttpPort      = max(min(intval($val), 65535), 0); break; }
                case 'PROXY_HTTP_USER' : {      $proxyHttpUser      = trim($val); break; }
                case 'PROXY_HTTP_PASS' : {      $proxyHttpPass      = trim($val); break; }
                case 'PROXY_SOCKS5_IP' : {      $proxySocks5Ip      = trim($val); break; }
                case 'PROXY_SOCKS5_PORT' : {    $proxySocks5Port    = max(min(intval($val), 65535), 0); break; }
                case 'PROXY_SOCKS5_USER' : {    $proxySocks5User    = trim($val); break; }
                case 'PROXY_SOCKS5_PASS' : {    $proxySocks5Pass    = trim($val); break; }
                case 'IP' : {                   $reqHost            = trim($val); break; }
                case 'PORT' : {                 $reqPort            = max(min(intval($val), 65535), 0); break; }
                case 'REFERER' : {              $options[CURLOPT_REFERER] = $val; break; }
                case 'SSL_VERIFYPEER' : {       $options[CURLOPT_SSL_VERIFYPEER] = !!$val; break; }
            }
        }
        // Head请求类型不获取BODY
        if($options[CURLOPT_CUSTOMREQUEST] === 'HEAD') $options[CURLOPT_NOBODY] = true;
        // 请求主机
        if(strlen($reqHost) > 0){
            $urlParseData                       = parse_url($url);
            if(is_array($urlParseData)){
                $urlHost                        = $urlParseData['host']??'';
                $urlPort                        = $urlParseData['port']??'';
                $options[CURLOPT_CONNECT_TO]    = [$urlHost.':'.$urlPort.':'.$reqHost.':'.($reqPort>0?$reqPort:$urlPort)];
            }else{
                unset($options[CURLOPT_CONNECT_TO]);
            }
        }else{
            unset($options[CURLOPT_CONNECT_TO]);
        }
        // HTTP认证
        $reqHttpAuthList    = [];
        if(strlen($reqHttpUser) > 0 || strlen($reqHttpPass) > 0){
            switch (strtolower($reqHttpAuthType)){
                case 'basic':{  // Basic
                    $options[CURLOPT_HTTPAUTH]      = CURLAUTH_BASIC;
                    $options[CURLOPT_USERPWD]       = $reqHttpUser.':'.$reqHttpPass;
                    break;
                }
                case 'bearer':{ // Bearer
                    $options[CURLOPT_HTTPAUTH]      = CURLAUTH_BEARER;
                    $options[CURLOPT_XOAUTH2_BEARER]= $reqHttpPass;
                    break;
                }
                default:{
                    if(strlen($reqHttpAuthType) > 0) $reqHttpAuthList['Authorization'] = $reqHttpAuthType;
                }
            }
        }else{
            unset($options[CURLOPT_HTTPAUTH], $options[CURLOPT_USERPWD]);
        }
        // 代理
        if(strlen($proxyHttpIp) > 0){           // HTTP代理
            $isProxy                            = true;
            $options[CURLOPT_PROXYTYPE]         = CURLPROXY_HTTP;
            $options[CURLOPT_PROXYAUTH]         = CURLAUTH_BASIC;
            $options[CURLOPT_PROXY]             = $proxyHttpIp;
            $options[CURLOPT_PROXYPORT]         = $proxyHttpPort;
            $options[CURLOPT_PROXYUSERPWD]      = trim($proxyHttpUser.':'.$proxyHttpPass);
        }else if(strlen($proxySocks5Ip) > 0){   // SOCKS5代理
            $isProxy                            = true;
            $options[CURLOPT_PROXYTYPE]         = CURLPROXY_SOCKS5_HOSTNAME;
            $options[CURLOPT_PROXYAUTH]         = CURLAUTH_BASIC;
            $options[CURLOPT_PROXY]             = $proxySocks5Ip;
            $options[CURLOPT_PROXYPORT]         = $proxySocks5Port;
            $options[CURLOPT_PROXYUSERPWD]      = trim($proxySocks5User.':'.$proxySocks5Pass);
        }else{
            unset($options[CURLOPT_PROXYTYPE], $options[CURLOPT_PROXY], $options[CURLOPT_PROXYPORT], $options[CURLOPT_PROXYAUTH], $options[CURLOPT_PROXYUSERPWD], $options[CURLOPT_PROXYHEADER]);
        }
        if($isProxy){
            if(isset($options[CURLOPT_PROXYUSERPWD]) && strlen($options[CURLOPT_PROXYUSERPWD]) < 2) unset($options[CURLOPT_PROXYUSERPWD], $options[CURLOPT_PROXYAUTH]);
            if(isset($options[CURLOPT_PROXYHEADER]) && count($options[CURLOPT_PROXYHEADER]) < 1) unset($options[CURLOPT_PROXYHEADER]);
        }
        // HEADER
        $headerList             = [
            'connection'        => 'close',
            'cache-control'     => 'max-age=0',
            'accept'            => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'accept-Language'   => 'en-US,en;q=0.8,zh;q=0.6',
            'dnt'               => '1',
        ];
        if(count($reqHttpAuthList) > 0) $headerList = array_merge($headerList, $reqHttpAuthList);
        $cookieList             = [];
        if(isset($param['HEADER'])){
            $headerData                                     = $param['HEADER'];
            if(is_string($headerData)){
                $pos                                        = strpos($headerData, ':');
                if($pos !== false){
                    $headerKey                              = substr($headerData, 0, $pos);
                    $headerVal                              = substr($headerData, $pos+1);
                    if(strtoupper($headerKey) === 'COOKIE'){
                        $cookieList[$headerKey]             = $headerVal;
                    }else{
                        $headerList[strtolower($headerKey)] = $headerVal;
                    }
                }
            }else if(is_array($headerData) && count($headerData) > 0){
                foreach($headerData as $name => $value){
                    if(!is_string($value)) continue;
                    if(is_string($name)){
                        $headerList[strtolower($name)]  = $value;
                    }else if(is_int($name)){
                        $pos                            = strpos($value, ':');
                        if($pos !== false){
                            $headerKey                  = substr($value, 0, $pos);
                            $headerVal                  = substr($value, $pos+1);
                            if(strtoupper($headerKey) === 'COOKIE'){
                                $cookieList[$headerKey] = $headerVal;
                            }else{
                                $headerList[strtolower($headerKey)] = $headerVal;
                            }
                        }
                    }
                }
            }
        }
        // COOKIE
        if(isset($param['COOKIE'])){
            $cookieData                 = $param['COOKIE'];
            if(is_string($cookieData)){
                foreach (explode(';', $cookieData) as $cookieTmp){
                    $cookieTmp          = trim($cookieTmp);
                    $pos                = strpos($cookieTmp, '=');
                    if($pos !== false){
                        $cookieKey      = substr($cookieTmp, 0, $pos);
                        $cookieVal      = substr($cookieTmp, $pos+1);
                        $cookieList[$cookieKey] = $cookieVal;
                    }
                }
            }else if(is_array($cookieData) && count($cookieData) > 0){
                foreach($cookieData as $name => $value){
                    if(!is_string($name) || !is_string($value)) continue;
                    $cookieList[$name]  = $value;
                }
            }
        }
        if(count($cookieList) > 0){
            $cookieStr          = '';
            foreach($cookieList as $name => $value) $cookieStr .= (strlen($cookieStr) < 1 ? '' : '; ').$name.'='.$value;
            $options[CURLOPT_COOKIE] = $cookieStr;
        }else{
            unset($options[CURLOPT_COOKIE]);
        }
        // POST类别和文件
        $postDataStr            = '';
        if(isset($param['POST_DATA'])){
            $postFile           = $param['POST_FILE']??[];
            $postData           = $param['POST_DATA'];
            $isBoundary         = strtoupper($param['POST_TYPE']??'') === 'BOUNDARY';
            if($isBoundary || count($postFile) > 0){
                $boundaryFlag       = '----------SRV'.CommString::genRandStr(27);
                $postDataAry        = [];
                if(is_string($postData)){
                    if(strlen(trim($postData)) > 0) foreach(explode('&', trim($postData)) as $val){
                        $pos        = strpos($val, '=');
                        if($pos !== false){
                            $postDataAry[substr($val, 0, $pos)] = urldecode(substr($val, $pos+1));
                        }else{
                            $postDataAry[$val] = '';
                        }
                    }
                }else if(is_array($postData)){
                    $postDataAry    = $postData;
                }
                if(count($postDataAry) > 0) foreach($postDataAry as $name => $val){
                    $postDataStr    .= '--'.$boundaryFlag.$br;
                    $postDataStr    .= 'Content-Disposition: form-data; name="'.$name.'"'.$br;
                    $postDataStr    .= 'Content-Type: text/plain'.$br;
                    $postDataStr    .= $br.$val.$br;
                }
                foreach($postFile as $name => $fileUrl){
                    if(!file_exists($fileUrl)) continue;
                    $fileContent    = '';
                    if(!CommFile::getContent($fileUrl, $fileContent)) continue;
                    $fileType       = CommFile::getMime($fileUrl);
                    $fileName       = basename($fileUrl);
                    $postDataStr    .= '--'.$boundaryFlag.$br;
                    $postDataStr    .= 'Content-Disposition: form-data; name="'.$name.'"; filename="'.$fileName.'"'.$br;
                    strlen($fileType) > 0 && $postDataStr .= 'Content-type: '.$fileType.$br;
                    $postDataStr    .= $br.$fileContent.$br;
                }
                if(strlen($postDataStr) > 0) $postDataStr .= '--'.$boundaryFlag.'--'.$br;
                if(!isset($headerList['content-type'])) $headerList['content-type'] = 'multipart/form-data; boundary='.$boundaryFlag;
            }else{
                if(is_string($postData)){
                    $postDataStr        = $postData;
                }else if(is_array($postData)){
                    foreach($postData as $key => $val) $postDataStr .= (strlen($postDataStr) > 0 ? '&' : '').$key.'='.rawurlencode(is_string($val) ? $val : @json_encode($val));
                }
                if(!isset($headerList['content-type'])) $headerList['content-type'] = 'application/x-www-form-urlencoded';
            }
        }
        $postDataStrLength                  = strlen($postDataStr);
        if($postDataStrLength > 0){
            $headerList['content-length']   = $postDataStrLength;
            $options[CURLOPT_POST]          = true;
            $options[CURLOPT_POSTFIELDS]    = $postDataStr;
        }else{
            $options[CURLOPT_POST]          = false;
            unset($options[CURLOPT_POSTFIELDS]);
        }
        if(count($headerList) > 0){
            $headerAry                      = [];
            foreach($headerList as $name => $value) $headerAry[] = ucfirst(preg_replace_callback('/[^a-z\d]+(\S)/i', function($match){ return strtoupper($match[1]);}, $name)).': '.$value;
            $options[CURLOPT_HTTPHEADER]    = $headerAry;
        }else{
            unset($options[CURLOPT_HTTPHEADER]);
        }
        $ch                     = curl_init();
        if(count($options) > 0) curl_setopt_array($ch, $options);
        $rspContent             = curl_exec($ch);
        $curlErrno              = curl_errno($ch);
        $curlError              = curl_error($ch);
        $rspData                = curl_getinfo($ch);
        curl_close($ch);
        // 分析
        $protocol               = 'http://';
        $hostPort               = '';
        if(preg_match("/(.*:\/\/)/", $url, $match)){
            $protocol           = strtolower(trim($match[1]));
            if(preg_match("/([^\/\\\\]+)(.*)/", substr($url, strlen($protocol)), $match)){
                $hostPort       = trim($match[1]);
            }else{
                $hostPort       = $url;
            }
        }
        $hostPortData           = explode(':', $hostPort);
        $serverName             = $hostPortData[0]??'';
        $serverPort             = intval($hostPortData[1]??0);
        $ip                     = $reqHost;
        $port                   = $reqPort;
        if($port < 1 && $serverPort > 0) $port = $serverPort;
        $rspData['ip']          = $rspData['primary_ip']??$ip;
        $rspData['port']        = $rspData['primary_port']??$port;
        $rspData['protocol']    = $protocol;
        $rspData['host']        = $serverName;
        $rspData['certinfo']    = [];
        $rspData['errno']       = $curlErrno;
        $rspData['error']       = $curlError;
        $rspData['charset']     = '';
        $rspData['set_cookie']  = [];
        $rspData['headList']    = [];
        if(!is_string($rspContent)){
            $rspData['head']    = '';
            $rspData['body']    = '';
            $rspData['result']  = false;
        }else{
            if($isProxy && strpos(strtolower(substr($rspContent, 0, 32)), ' connection ') > 0){
                list($proxyHeader, $rspHeader, $rspBody) = explode($br.$br, $rspContent, 3);
                $rspData['proxyHead'] = $proxyHeader;
            }else{
                list($rspHeader, $rspBody) = explode($br.$br, $rspContent, 2);
            }
            if(!is_string($rspHeader)) $rspHeader = '';
            if(!is_string($rspBody)) $rspBody = '';
            // 兼容2次header
            if(preg_match('/^HTTP\/1.\d\s+\d+/', substr($rspBody, 0, 13))) list($rspHeader, $rspBody) = explode($br.$br, $rspBody, 2);
            $rspData['head']    = $rspHeader;
            $rspData['body']    = $rspBody;
            $rspData['result']  = $rspData['http_code'] >= 100 && $rspData['http_code'] < 400;
            $rspHeaderList      = explode("\n", $rspHeader);
            unset($rspHeaderList[0]);
            if(count($rspHeaderList) > 0) foreach($rspHeaderList as $val){
                $pos            = strpos($val, ':');
                if($pos === false) continue;
                $tmpKey         = substr($val, 0, $pos);
                $tmpVal         = trim(substr($val, $pos+1));
                $tmpKeyLower    = strtolower($tmpKey);
                switch($tmpKeyLower){
                    case 'content-type': {
                        $posTmp     = strpos($tmpVal, ';');
                        $rspData['content_type'] = trim($posTmp === false ? $tmpVal : substr($tmpVal, 0, $posTmp));
                        if(preg_match("/charset=([^;=\/\s]*)/i", $tmpVal, $match)) $rspData['charset'] = strtoupper($match[1]);
                        break;
                    }
                    case 'content-length': {
                        $rspData['download_content_length'] = intval($tmpVal);
                        break;
                    }
                    case 'date': {
                        $rspData['filetime'] = intval(strtotime($tmpVal));
                        break;
                    }
                    case 'set-cookie': {    // cookie
                        $posTmp     = strpos($tmpVal, ';');
                        if($posTmp > 0){
                            list($cookieKey, $cookieVal) = explode('=', substr($tmpVal, 0, $posTmp), 2);
                            if(strlen($cookieKey) > 0 && is_string($cookieVal)) $rspData['set_cookie'][$cookieKey] = urldecode($cookieVal);
                        }
                        break;
                    }
                    default: {
                        $rspData['headList'][$tmpKeyLower] = $tmpVal;
                    }
                }
            }
        }
        return $rspData;
    }

    /**
     * @param string $url
     * @param array $param
     * @param int $bodyLimit
     * @return array
     * getUrlInfo
     */
    public static function getUrlInfo(string $url, array $param = [], int $bodyLimit = 0):array
    {
        /** $param 参数
         * VERSION                  设置请求协议版本[string: 1.0,1.1,default:1.1]
         * COOKIE                   COOKIE数据[array(key=>value)]
         * HEADER                   Header数据[string,array(key=>value)]
         * USER                     HTTPAUTH用户名[string:Basic]
         * PASS                     HTTPAUTH密码[string:Basic]
         * AUTH_TYPE                HTTPAUTH类别[string:Basic/Bearer]
         * POST_DATA                POST的数据[string:name=value&name2=value2,array(key1=>value1,key2=>value2)]
         * POST_FILE                POST的文件路径数组[array(key=>path)]
         * POST_TYPE                POST数据方式[string:BOUNDARY,NONE,default:NONE]
         * IS_NOBODY                是否获取正文[bool]
         * IS_FOLLOWLOCATION        是否自动重定向[bool]
         * FOLLOWLOCATION_NUM       重定向最大次数[int:0-999,default:22]
         * TIMEOUT                  超时时间秒[int:0-999,default:60]
         * IP                       指定服务器IP[string:ipv4、ipv6]
         * PORT                     指定服务器端口[int:1-65535,default:0]
         * SSL                      指定请求SSL信息[array(key=>value)]
         * METHOD                   请求方式类型[string:GET,POST,HEAD,PUT,default:GET]
         * REDIRECT_COUNT           重定向了多少次[int:0]
         * BIND_IP                  设置请求使用本机的某个IP[string:ipv4、ipv6]
         * IS_KEEPALIVE             是否保持Keepalive状态[bool]
         * PROXY_HTTP_IP            HTTP代理服务器IP[string:ipv4、ipv6]
         * PROXY_HTTP_PORT          HTTP代理服务器端口[int:1-65535,default:0]
         * PROXY_HTTP_USER          HTTP代理服务器USER
         * PROXY_HTTP_PASS          HTTP代理服务器PASS
         * PROXY_SOCKS5_IP          SOCKS5代理服务器IP[string:ipv4、ipv6]
         * PROXY_SOCKS5_PORT        SOCKS5代理服务器端口[int:1-65535,default:0]
         * PROXY_SOCKS5_USER        SOCKS5代理服务器USER
         * PROXY_SOCKS5_PASS        SOCKS5代理服务器PASS
         */
        $rspData = [
//            'url'                           => '',      // 最后一个有效的url地址
            'ip'                            => '',      // 最后一个有效的ip地址
            'content_type'                  => '',      // 最后一个文档类型
            'http_code'                     => 0,       // 最后一个收到的HTTP代码
            'header_size'                   => 0,       // header部分的大小
            'request_size'                  => 0,       // 在HTTP请求中有问题的请求的大小
            'filetime'                      => -1,      // 远程获取文档的时间(时间戳),无法获取则返回值为-1
            'ssl_verify_result'             => 0,       // 结果的ssl认证所要求的核查设置
            'redirect_count'                => 0,       // 重定向次数
            'total_time'                    => 0,       // 最后一次传输所消耗的时间
            'namelookup_time'               => 0,       // 域名解析所消耗的时间
            'connect_time'                  => 0,       // 建立连接所消耗的时间
            'pretransfer_time'              => 0,       // 从建立连接到准备传输所使用的时间
            'size_upload'                   => 0,       // 上传数据量的总值
            'size_download'                 => 0,       // 下载数据量的总值
            'speed_download'                => 0,       // 平均下载速度
            'speed_upload'                  => 0,       // 平均上传速度
            'download_content_length'       => -1,      // 从Content-Length:中读取的下载长度
            'upload_content_length'         => -1,      // 上传内容大小的说明
            'starttransfer_time'            => 0,       // 从建立连接到传输开始所使用的时间
            'redirect_time'                 => 0,       // 在事务传输开始前重定向所使用的时间
            'certinfo'                      => [],      // 证书信息
//            'errno'                         => 0,       // 错误代号
//            'error'                         => '',      // 错误消息
            'head'                          => '',      // head
            'body'                          => '',      // body
            'charset'                       => '',      // charset
            'protocol'                      => '',      // protocol
            'host'                          => '',      // host
            'port'                          => '',      // port
            'result'                        => false,   // 结果
            'set_cookie'                    => [],      // cookie数组
            'headList'                      => [],      // head数组
        ];
        $setInfo                            = [];       // 设置初始信息
        $setInfo['IS_NOBODY']               = false;    // 是否不获取内容
        $setInfo['IS_FOLLOWLOCATION']       = true;     // 是否允许重定向
        $setInfo['FOLLOWLOCATION_NUM']      = 20;       // 重定向最大次数
        $setInfo['TIMEOUT']                 = 60;       // 超时
        $setInfo['IP']                      = '';       // 主机IP
        $setInfo['PORT']                    = 0;        // 端口
        $setInfo['METHOD']                  = 'GET';    // GET/POST/HEAD/PUT
        $setInfo['REDIRECT_COUNT']          = 0;        // 重定向次数
        $setInfo['BIND_IP']                 = '';       // 绑定服务器的IP
        $setInfo['IS_KEEPALIVE']            = false;    // 是否长连接
        $setInfo['SSL']                     = [];       // SSL参数
        $setInfo['IS_REFRESH']              = false;    // 是否HTML-REFRESH
        if(count($param) > 0) foreach($param as $key => $val){
            switch($key){
                case 'IS_NOBODY' : {            // 是否获取正文
                    $setInfo['IS_NOBODY']               = !!$val;
                    break;
                }
                case 'IS_FOLLOWLOCATION' : {    // 是否自动重定向
                    $setInfo['IS_FOLLOWLOCATION']       = !!$val;
                    break;
                }
                case 'FOLLOWLOCATION_NUM' : {   // 重定向最大次数
                    $setInfo['FOLLOWLOCATION_NUM']      = min(max(intval($val), 0), 999);
                    break;
                }
                case 'TIMEOUT' : {              // 超时时间秒
                    $setInfo['TIMEOUT']                 = min(max(intval($val), 0), 999);
                    break;
                }
                case 'IP' : {                   // 指定服务器IP
                    $setInfo['IP']                      = trim($val);
                    break;
                }
                case 'PORT' : {                 // 指定服务器端口
                    $setInfo['PORT']                    = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'SSL' : {                  // 指定请求SSL信息
                    $setInfo['SSL']                     = array_merge($setInfo['SSL'], $val);
                    break;
                }
                case 'METHOD' : {               // 请求方式类型
                    $setInfo['METHOD']                  = strtoupper($val);
                    break;
                }
                case 'REDIRECT_COUNT' : {       // 重定向来多少次
                    $rspData['redirect_count']          = $setInfo['REDIRECT_COUNT'] = max(intval($val), 0);
                    break;
                }
                case 'REDIRECT_TIME' : {        // 重定向时长
                    $rspData['redirect_time']            = floatval($val);
                    break;
                }
                case 'BIND_IP' : {              // 设置请求使用本机的某个IP
                    $setInfo['BIND_IP']                 = trim($val);
                    break;
                }
                case 'IS_KEEPALIVE' : {         // 是否保持Keepalive状态
                    $setInfo['IS_KEEPALIVE']            = !!$val;
                    break;
                }
                case 'IS_REFRESH' : {           // 是否HTML-REFRESH
                    $setInfo['IS_REFRESH']              = !!$val;
                    break;
                }
                case 'PROXY_HTTP_IP' : {        // HTTP代理服务器IP
                    $setInfo['PROXY_HTTP_IP']           = trim($val);
                    break;
                }
                case 'PROXY_HTTP_PORT' : {      // HTTP代理服务器端口
                    $setInfo['PROXY_HTTP_PORT']         = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'PROXY_HTTP_USER' : {      // HTTP代理服务器USER
                    $setInfo['PROXY_HTTP_USER']         = trim($val);
                    break;
                }
                case 'PROXY_HTTP_PASS' : {      // HTTP代理服务器PASS
                    $setInfo['PROXY_HTTP_PASS']         = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_IP' : {      // SOCKS5代理服务器IP
                    $setInfo['PROXY_SOCKS5_IP']         = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_PORT' : {    // SOCKS5代理服务器端口
                    $setInfo['PROXY_SOCKS5_PORT']       = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'PROXY_SOCKS5_USER' : {    // SOCKS5代理服务器USER
                    $setInfo['PROXY_SOCKS5_USER']       = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_PASS' : {    // SOCKS5代理服务器PASS
                    $setInfo['PROXY_SOCKS5_PASS']       = trim($val);
                    break;
                }
            }
        }
        if(!$setInfo['IS_FOLLOWLOCATION']){     // 不允许重定向
            $setInfo['FOLLOWLOCATION_NUM']              = 0;
            $rspData['redirect_count']                  = $setInfo['REDIRECT_COUNT'] = 0;
        }
        $setInfo['IS_NOBODY'] && $setInfo['METHOD'] = 'HEAD';
        $rspData['url'] = $url = trim($url);
        $toUrl          = '';
        $protocol       = 'http://';
        if(preg_match("/(.*:\/\/)/", $url, $match)){
            $protocol   = strtolower(trim($match[1]));
            $url        = substr($url, strlen($protocol));
        }
        if(preg_match("/([^\/\\\\]+)(.*)/", $url, $match)){
            $hostPort   = trim($match[1]);
            $toUrl      = trim($match[2]);
        }else{
            $hostPort   = $url;
        }
        strlen($toUrl) < 1 && $toUrl = '/';
        $hostPortData       = explode(':', $hostPort);
        $serverName         = Idna::encode($hostPortData[0]??'');
        $serverPort         = intval($hostPortData[1]??0);
        $ip                 = trim($setInfo['IP']);
        $port               = intval($setInfo['PORT']);
        if($port < 1 && $serverPort > 0) $port = $serverPort;
        $startTimeMicro     = CommTime::diffTimeMicro(0);
        if(strlen($ip) < 3 || !(CommValid::isIp($ip) || CommValid::isIpv6($ip))){
            $ip             = gethostbyname($serverName);
            $rspData['namelookup_time']  = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
            if(!(CommValid::isIp($ip) || CommValid::isIpv6($ip))){
                $rspData['errno']   = 6;
                $rspData['error']   = 'Couldn\'t resolve host.';
                return $rspData;
            }
        }
        $serverHostStr      = $serverName;
        $isSsl              = false;
        if($protocol === 'http://'){
            $port === 0 && $port = 80;
            $port !== 80 && $serverHostStr .= ':'.$serverPort;
            $serverHostPort = $ip.':'.$port;
        }else if($protocol == 'https://' || $protocol == 'ssl://' || $protocol == 'tls://'){
            $port === 0 && $port = 443;
            $port !== 443 && $serverHostStr .= ':'.$serverPort;
            $serverHostPort = 'ssl://'.$ip.':'.$port;
            $isSsl          = true;
        }else{
            $serverHostPort = $ip.':'.$port;
        }
        $rspData['ip']       = $ip;
        $rspData['port']     = $port;
        $rspData['protocol'] = $protocol;
        $rspData['host']     = $serverHostStr;
        if($port < 1 || $port > 65535){
            $rspData['errno']        = 6;
            $rspData['error']        = 'Couldn\'t resolve port.';
            return $rspData;
        }
        $socketData                             = [];
        $socketData['ssl']['verify_peer']       = false;
        $socketData['ssl']['verify_peer_name']  = false;
        if(!CommValid::isIp($serverName) && !CommValid::isIpv6($serverName)) $socketData['ssl']['peer_name'] = $serverName; //指定主机名
        count($setInfo['SSL']) > 0 && $socketData['ssl'] = array_merge($socketData['ssl'], $setInfo['SSL']);
        $bindIp                 = trim($setInfo['BIND_IP']);
        if(strlen($bindIp) > 0 && (CommValid::isIp($bindIp) || CommValid::isIpv6($bindIp))) $socketData['socket']['bindto'] = $bindIp;
        $rspData['certinfo']    = $socketData['ssl'];
        $serverTimeOut          = intval($setInfo['TIMEOUT']);
        $br                     = "\r\n";
        $errno                  = 0;
        $errstr                 = '';
        $proxyHttpIp            = trim($setInfo['PROXY_HTTP_IP']??'');
        $proxySocks5Ip          = trim($setInfo['PROXY_SOCKS5_IP']??'');
        if(strlen($proxySocks5Ip) > 0){ // SOCKS5代理模式
            $proxySocks5Port            = intval($setInfo['PROXY_SOCKS5_PORT']??0);
            $proxySocks5User            = trim($setInfo['PROXY_SOCKS5_USER']??'');
            $proxySocks5Pass            = trim($setInfo['PROXY_SOCKS5_PASS']??'');
            if($proxySocks5Port < 1 || $proxySocks5Port > 65535){
                $rspData['errno']       = 6;
                $rspData['error']       = 'SOCKS5 Proxy Error port.';
                return $rspData;
            }
            if(!(CommValid::isIp($proxySocks5Ip) || CommValid::isIpv6($proxySocks5Ip))){
                $proxySocks5IpLookUp    = trim(gethostbyname($proxySocks5Ip));
                $rspData['namelookup_time_proxy']  = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
                if(!(CommValid::isIp($proxySocks5IpLookUp) || CommValid::isIpv6($proxySocks5IpLookUp))){
                    $rspData['errno']   = 6;
                    $rspData['error']   = 'SOCKS5 Proxy Error host.';
                    return $rspData;
                }
                $proxySocks5Ip          = $proxySocks5IpLookUp;
            }
            $context                    = @stream_context_create($socketData);
            $fpSocket                   = @stream_socket_client('tcp://'.$proxySocks5Ip.':'.$proxySocks5Port, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context);
            if(!$fpSocket){
                $rspData['errno']       = 7;
                $rspData['error']       = 'Proxy Failed to connect() to host or proxy.';
                return $rspData;
            }
            @stream_set_blocking($fpSocket, true);
            if(strlen($proxySocks5User) > 0){
                @fwrite($fpSocket, "\05\01\02");
                if(fread($fpSocket, 2) !== "\05\02"){
                    @fclose($fpSocket);
                    $rspData['errno']   = 97;
                    $rspData['error']   = 'No authentication method was acceptable.';
                    return $rspData;
                }
                @fwrite($fpSocket, "\01".chr(strlen($proxySocks5User)).$proxySocks5User.chr(strlen($proxySocks5Pass)).$proxySocks5Pass);
                if(fread($fpSocket, 2) !== "\01\00"){
                    @fclose($fpSocket);
                    $rspData['errno']   = 97;
                    $rspData['error']   = 'No authentication method was acceptable Too.';
                    return $rspData;
                }
            }else{
                @fwrite($fpSocket, "\05\01\00");
                if(fread($fpSocket, 2) !== "\05\00"){
                    @fclose($fpSocket);
                    $rspData['errno']   = 7;
                    $rspData['error']   = 'Proxy Failed.';
                    return $rspData;
                }
            }
            list($hostStr, $portTmp) = explode(':', $serverHostStr.':', 2);
            unset($portTmp);
            if(CommValid::isIp($hostStr)){          // 0x01 IPv4地址,4字节长度
                $socks5Send         = "\05\01\00\01".inet_pton($hostStr).pack('n', $port);
            }else if(CommValid::isIpv6($hostStr)){  // 0x04 IPv6地址,16个字节长度
                $socks5Send         = "\05\01\00\04".inet_pton($hostStr).pack('n', $port);
            }else{                                  // 0x03 域名,第一个字节为域名长度,剩余的内容为域名,没有\0结尾
                $socks5Send         = "\05\01\00\03".chr(strlen($hostStr)).$hostStr.pack('n', $port);
            }
            @fwrite($fpSocket, $socks5Send);
            if(substr(fread($fpSocket, 10), 0, 3) !== "\05\00\00"){
                @fclose($fpSocket);
                $rspData['errno']    = 7;
                $rspData['error']    = 'Proxy Failed to connect() to host or proxy Check.';
                return $rspData;
            }
            if($isSsl && !@stream_socket_enable_crypto($fpSocket, true, STREAM_CRYPTO_METHOD_ANY_CLIENT)){  // 支持SSL
                @fclose($fpSocket);
                $rspData['errno']    = 7;
                $rspData['error']    = 'Proxy SSL Failed.';
                return $rspData;
            }
        }else if(strlen($proxyHttpIp) > 0){ // HTTP代理模式
            $proxyHttpPort              = intval($setInfo['PROXY_HTTP_PORT'] ?? 0);
            $proxyHttpUser              = trim($setInfo['PROXY_HTTP_USER']??'');
            $proxyHttpPass              = trim($setInfo['PROXY_HTTP_PASS']??'');
            if($proxyHttpPort < 1 || $proxyHttpPort > 65535){
                $rspData['errno']       = 6;
                $rspData['error']       = 'HTTP Proxy Error port.';
                return $rspData;
            }
            if(!(CommValid::isIp($proxyHttpIp) || CommValid::isIpv6($proxyHttpIp))){
                $proxyHttpIpLookUp      = trim(gethostbyname($proxyHttpIp));
                $rspData['namelookup_time_proxy']  = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
                if(!(CommValid::isIp($proxyHttpIpLookUp) || CommValid::isIpv6($proxyHttpIpLookUp))){
                    $rspData['errno']   = 6;
                    $rspData['error']   = 'HTTP Proxy Error host.';
                    return $rspData;
                }
                $proxyHttpIp            = $proxyHttpIpLookUp;
            }
            $proxyAuth              = strlen($proxyHttpUser) > 0 ? $proxyHttpUser.':'.$proxyHttpPass : '';
            $context                = @stream_context_create($socketData);
            $fpSocket               = @stream_socket_client('tcp://'.$proxyHttpIp.':'.$proxyHttpPort, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context);
            if(!$fpSocket){
                $rspData['errno']    = 7;
                $rspData['error']    = 'Proxy Failed to connect() to host or proxy.';
                return $rspData;
            }
            @stream_set_blocking($fpSocket, true);
            $proxyHeads             = 'CONNECT '.$serverHostStr.' HTTP/'.($param['VERSION']??'1.1').$br;
            if(strlen($proxyAuth) > 0) $proxyHeads .= 'Proxy-Authorization: Basic '.base64_encode($proxyAuth).$br;
            $proxyHeads             .= $br;
            @fwrite($fpSocket, $proxyHeads);
            $proxyRsp               = fread($fpSocket, 1024);
            if(!preg_match('/^HTTP\/\d\.\d 200/', $proxyRsp)){
                @fclose($fpSocket);
                $rspData['errno']   = 7;
                $rspData['error']   = 'Proxy Failed to connect() to host or proxy Too.';
                return $rspData;
            }
            if($isSsl && !@stream_socket_enable_crypto($fpSocket, true, STREAM_CRYPTO_METHOD_ANY_CLIENT)){   // 支持SSL
                @fclose($fpSocket);
                $rspData['errno']   = 7;
                $rspData['error']   = 'Proxy SSL Failed.';
                return $rspData;
            }
        }else{
            $context                = @stream_context_create($socketData);
            $fpSocket               = @stream_socket_client($serverHostPort, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context); // ssl://ip:port or ip:port or ip
            if(!$fpSocket){
                $rspData['errno']   = 7;
                $rspData['error']   = 'Failed to connect() to host.';
                return $rspData;
            }
            @stream_set_blocking($fpSocket, true);
        }
        $rspData['connect_time']    = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
        $rspData['errno']           = $errno;
        $rspData['error']           = $errstr;
        $heads                      = $setInfo['METHOD'].' '.$toUrl.' HTTP/'.($param['VERSION'] ?? '1.1').$br;
        $heads                      .= 'Host: '.$serverHostStr.$br;
        $headerData                 = $param['HEADER'] ?? [];
        $headerList                 = [];
        if(is_string($headerData)){
            $headerData     = trim($headerData);
            if(strlen($headerData) > 0){
                $heads      .= $headerData.$br;
                foreach(explode("\n", $headerData) as $line){
                    $line   = trim($line);
                    if(strlen($line) < 3) continue;
                    $pos    = strpos($line, ':');
                    if($pos !== false) $headerList[strtolower(substr($line, 0, $pos))][] = substr($line, $pos+1);
                }
            }
        }else if(is_array($headerData) && count($headerData) > 0){
            foreach($headerData as $name => $value){
                if(!is_string($value)) continue;
                if(is_numeric($name)){
                    $value          = trim($value);
                    if(strlen($value) > 0){
                        $heads      .= $value.$br;
                        foreach(explode("\n", $value) as $line){
                            $line   = trim($line);
                            if(strlen($line) < 3) continue;
                            $pos    = strpos($line, ':');
                            if($pos !== false) $headerList[strtolower(substr($line, 0, $pos))][] = substr($line, $pos+1);
                        }
                    }
                }else if(is_string($name)){
                    $name           = trim($name);
                    if(strlen($name) < 1) continue;
                    $value          = trim($value);
                    $headerList[strtolower($name)][] = $value;
                    $heads          .= $name.': '.$value.$br;
                }
            }
        }
        $headTpl        = 'Connection: '.($setInfo['IS_KEEPALIVE'] ? 'keep-alive' : 'close').$br;
        $headTpl        .= 'Cache-Control: max-age=0'.$br;
        $headTpl        .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'.$br;
        $headTpl        .= 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'.$br;
        $headTpl        .= 'Accept-Encoding: gzip, deflate'.$br;
        $headTpl        .= 'Accept-Language: en-US,en;q=0.8,zh;q=0.6'.$br;
        $headTpl        .= 'DNT: 1'.$br;
        foreach(explode("\n", $headTpl) as $line){
            $line           = trim($line);
            if(strlen($line) < 3) continue;
            $pos            = strpos($line, ':');
            if($pos !== false){
                $headerKey  = strtolower(substr($line, 0, $pos));
                if(!isset($headerList[$headerKey])) $heads .= $line.$br;
                $headerList[$headerKey][] = substr($line, $pos+1);
            }
        }
        $isSetContentType   = isset($headerList['content-type']);
        $cookieData         = $param['COOKIE'] ?? '';
        if(is_string($cookieData) && strlen($cookieData) > 0){
            $heads          .= 'Cookie: '.$cookieData.$br;
        }else if(is_array($cookieData) && count($cookieData) > 0){
            foreach($cookieData as $name => $value){
                $heads      .= 'Cookie: '.$name.'='.$value.$br;
            }
        }
        $httpAuthUser       = trim($param['USER'] ?? '');
        $httpAuthPass       = trim($param['PASS'] ?? '');
        $httpAuthType       = trim($param['AUTH_TYPE'] ?? 'Basic');
        if(strlen($httpAuthUser) > 0 || strlen($httpAuthPass) > 0){
            switch (strtolower($httpAuthType)){
                case 'basic':{  // Basic
                    $heads  .= 'Authorization: Basic '.base64_encode($httpAuthUser.':'.$httpAuthPass).$br;
                    break;
                }
                case 'bearer':{ // Bearer
                    $heads  .= 'Authorization: Bearer '.$httpAuthPass.$br;
                    break;
                }
                default:{
                    if(strlen($httpAuthType) > 0) $heads .= 'Authorization: '.$httpAuthType.$br;
                }
            }
        }
        if($setInfo['METHOD'] === 'POST' || $setInfo['METHOD'] === 'PUT'){
            $isBoundary     = false;
            $postFile       = $param['POST_FILE'] ?? [];
            $postData       = $param['POST_DATA'] ?? '';
            if(!is_array($postFile)){
                @fclose($fpSocket);
                $rspData['errno'] = 61;
                $rspData['error'] = 'Failed POST File Error.';
                return $rspData;
            }
            if(count($postFile) > 0 || ($param['POST_TYPE'] ?? '') === 'BOUNDARY') $isBoundary = true;
            if($isBoundary){
                $boundaryFlag       = '----------SRV'.CommString::genRandStr(27);
                $postDataString     = '';
                $postDataArray      = [];
                if(is_string($postData)){
                    if(strlen(trim($postData)) > 0) foreach(explode('&', trim($postData)) as $val){
                        $pos        = strpos($val, '=');
                        if($pos !== false){
                            $postDataArray[substr($val, 0, $pos)] = urldecode(substr($val, $pos+1));
                        }else{
                            $postDataArray[$val] = '';
                        }
                    }
                }else if(is_array($postData)){
                    $postDataArray      = $postData;
                }else{
                    @fclose($fpSocket);
                    $rspData['errno']   = 60;
                    $rspData['error']   = 'Failed POST Data Error.';
                    return $rspData;
                }
                if(count($postDataArray) > 0) foreach($postDataArray as $name => $val){ // Data
                    $postDataString     .= '--'.$boundaryFlag.$br;
                    $postDataString     .= 'Content-Disposition: form-data; name="'.$name.'"'.$br;
                    $postDataString     .= 'Content-Type: text/plain'.$br;
                    $postDataString     .= $br.$val.$br;
                }
                if(count($postFile) > 0) foreach($postFile as $name => $fileUrl){   // File
                    if(!file_exists($fileUrl)){
                        @fclose($fpSocket);
                        $rspData['errno'] = 62;
                        $rspData['error'] = 'Failed POST File Not Exists.';
                        return $rspData;
                    }
                    $fileContent    = '';
                    if(!CommFile::getContent($fileUrl, $fileContent)){
                        @fclose($fpSocket);
                        $rspData['errno'] = 62;
                        $rspData['error'] = 'Failed POST File Read Error.';
                        return $rspData;
                    }
                    $fileType           = CommFile::getMime($fileUrl);
                    $fileName           = basename($fileUrl);
                    $postDataString     .= '--'.$boundaryFlag.$br;
                    $postDataString     .= 'Content-Disposition: form-data; name="'.$name.'"; filename="'.$fileName.'"'.$br;
                    strlen($fileType) > 0 && $postDataString .= 'Content-type: '.$fileType.$br;
                    $postDataString     .= $br.$fileContent.$br;
                }
                if(strlen($postDataString) > 0){
                    $postDataString     .= '--'.$boundaryFlag.'--'.$br;
                    $postDataLength     = strlen($postDataString);
                    $heads              .= 'Content-Length: '.$postDataLength.$br;
                    $heads              .= 'Content-Type: multipart/form-data; boundary='.$boundaryFlag.$br;
                    $heads              .= $br.$postDataString.$br;
                }
            }else{
                if(is_string($postData)){
                    $postDataString     = trim($postData);
                }else if(is_array($postData)){
                    $postDataString     = self::genPostDataStr($postData);
                }else{
                    @fclose($fpSocket);
                    $rspData['errno']   = 61;
                    $rspData['error']   = 'Failed POST Data Error.';
                    return $rspData;
                }
                $postDataLength         = strlen($postDataString);
                $heads                  .= 'Content-Length: '.$postDataLength.$br;
                if($postDataLength > 0){
                    if(!$isSetContentType) $heads .= 'Content-Type: application/x-www-form-urlencoded'.$br;
                    $heads              .= $br.$postDataString.$br;
                }
            }
            $rspData['upload_content_length'] = floatval(strlen($postDataString));
        }
        $heads                          .= $br;
        @fwrite($fpSocket, $heads);
        $rspData['pretransfer_time']    = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
        $rspData['request_size']        = strlen($heads);
        $isHead                         = true;
        $isChunked                      = false;
        $isGzip                         = false;
        $headInfoString                 = '';
        $bodyInfoSting                  = '';
        $headToUrl                      = '';
        $nextLength                     = 0;
        $sizeNum                        = 0;
        $remainingNum                   = 0;
        $maxCount                       = 3;
        @stream_set_timeout($fpSocket, $setInfo['TIMEOUT']);
        while(!$content = fgets($fpSocket)){
            if($maxCount < 1){
                @fclose($fpSocket);
                $rspData['errno']       = 52;
                $rspData['error']       = 'Return Empty.';
                return $rspData;
            }else{
                --$maxCount;
            }
        }
        $rspData['starttransfer_time']  = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
        $headArray                      = explode(' ', trim($content));
        if(count($headArray) > 1){
            $rspData['http_pro']        = trim($headArray[0]);
            $rspData['http_code']       = intval($headArray[1]);
        }else{
            @fclose($fpSocket);
            $rspData['errno']           = 51;
            $rspData['error']           = 'Return Error.';
            return $rspData;
        }
        $headInfoString         .= $content;
        while(true){
            if($isHead){
                $content        = fgets($fpSocket);
                if($content === false) break;   // ERROR
                $contentTrim    = trim($content);
                if(strlen($contentTrim) < 1){   // set No Header
                    $isHead     = false;
                    if(strlen($headToUrl) > 0){
                        @fclose($fpSocket);
                        $rspData['redirect_time']    += floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
                        $param['REDIRECT_TIME']     = $rspData['redirect_time'];
                        return self::getUrlInfo($headToUrl, $param);    // 执行重定向
                    }
                    continue;
                }
                $headInfoString .= $content;
                $tmp            = explode(':', $contentTrim);
                $tmpKey         = strtoupper(trim($tmp[0]));
                if($tmpKey === 'CONTENT-LENGTH'){
                    $remainingNum = $sizeNum = intval(trim($tmp[1]));
                    continue;
                }
                if(!$isChunked && $tmpKey === 'TRANSFER-ENCODING' && strpos(strtolower($tmp[1]), 'chunked') !== false) $isChunked = true;        // 分块传输
                if(!$isGzip && $tmpKey === 'CONTENT-ENCODING' && strpos(strtolower($tmp[1]), 'gzip') !== false) $isGzip = true;                  // gzip压缩
                if($tmpKey !== 'Transfer-Encoding'){
                    if($tmpKey === 'LOCATION' && ($rspData['http_code'] >= 300 && $rspData['http_code'] < 400) && $setInfo['IS_FOLLOWLOCATION']){  // 重定向 允许
                        if($setInfo['FOLLOWLOCATION_NUM']-- < 1){                       // 超过最大重定向
                            @fclose($fpSocket);
                            $rspData['errno']       = 47;
                            $rspData['error']       = 'Too many redirects.';
                            return $rspData;
                        }
                        $param['REDIRECT_COUNT']    = ++$setInfo['REDIRECT_COUNT'];     // 重定向次数+
                        unset($tmp[0]);
                        $tmpVal     = trim(implode(':', $tmp));
                        $headToUrl  = trim(strpos($tmpVal, '://') === false ? $protocol.$hostPort.($tmpVal[0] == '/' ? '' : '/').$tmpVal : $tmpVal);
                    }else{
                        if(strtolower(trim($tmp[0])) == 'set-cookie'){                  // 记录Cookie
                            unset($tmp[0]);
                            $tmpVal = trim(implode(':', $tmp));
                            if(!isset($param['COOKIE'])) $param['COOKIE'] = '';
                            $param['COOKIE'] .= ($param['COOKIE'] == '' ? '' : '; ').substr($tmpVal, 0, intval(strpos($tmpVal, ';')));
                        }
                    }
                }
            }else{
                if($setInfo['IS_NOBODY']) break;
                if($bodyLimit > 0 && strlen($bodyInfoSting) >= $bodyLimit) break;
                if($isChunked){                             // 分段传输
                    if($nextLength < 1){
                        $contentNext    = fgets($fpSocket);
                        if($contentNext === false) break;   // ERROR
                        $nextLength     = hexdec(trim($contentNext));
                        if($nextLength < 1) break;          // 结尾
                        $nextLength     += 2;
                        continue;
                    }else{
                        $content        = fread($fpSocket, $nextLength);
                        if($content === false) break;       // ERROR
                        $nextLength     -= strlen($content);
                        if($nextLength <= 0) $content = substr($content, 0, -2);
                        $bodyInfoSting .= $content;
                    }
                }else{
                    $content            = fgets($fpSocket, $sizeNum > 0 ? min(4096, $remainingNum+1) : 4096);
                    if($content === false) break;           // ERROR
                    $bodyInfoSting      .= $content;
                    if(strlen($content) < 1) break;         // 结束
                }
            }
            $remainingNum = $sizeNum - strlen($bodyInfoSting);
            if($sizeNum > 0 && $remainingNum <= 0) break;   // 指定的长度已经读取完成
            $metaData = @stream_get_meta_data($fpSocket);
            if(isset($metaData['timed_out']) && $metaData['timed_out']) break;
        }    // End while
        @fclose($fpSocket);
        if($setInfo['IS_REFRESH']){                         // 是否HTML-REFRESH
            if(preg_match_all('/<meta([^>]*?)\/?>(<\/meta>)*/i', $bodyInfoSting, $match)){
                /**
                 * @var array $match
                 */
                foreach($match[1] as $line){
                    if(preg_match('/http-equiv\s*=\s*[\"\']?refresh[\"\']?/i', $line) && preg_match('/content\s*=\s*[\"\']?([^\"\']+)/i', $line, $matchVal)){
                        $content    = trim($matchVal[1]);
                        $pos        = strpos($content, ';');
                        $content    = substr($content, $pos+1);
                        $pos        = strpos($content, '=');
                        $tmpVal     = trim(substr($content, $pos+1));
                        $headToUrl  = trim(strpos($tmpVal, '://') === false ? $protocol.$hostPort.($tmpVal[0] == '/' ? '' : '/').$tmpVal : $tmpVal);
                        if($setInfo['FOLLOWLOCATION_NUM']-- < 1){                   // 超过最大重定向
                            $rspData['errno']       = 47;
                            $rspData['error']       = 'Too many redirects.';
                            return $rspData;
                        }
                        $param['REDIRECT_COUNT']    = ++$setInfo['REDIRECT_COUNT']; // 重定向次数+
                        $rspData['redirect_time']   += floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
                        $param['REDIRECT_TIME']     = $rspData['redirect_time'];
                        return self::getUrlInfo($headToUrl, $param);                // 执行重定向
                    }
                }
            }
        }
        $rspData['total_time']      = floatval(sprintf('%.6f', CommTime::diffTimeMicro($startTimeMicro)/1000000));
        $fpSocket                   = null;
        if($isGzip && strlen($bodyInfoSting) > 1){
            $bodyInfoRes            = @gzdecode($bodyInfoSting);
            if(is_string($bodyInfoRes)) $bodyInfoSting = $bodyInfoRes;
        }
        // 兼容2次header
        if(preg_match('/^HTTP\/1.\d\s+\d+/', substr($bodyInfoSting, 0, 13))) list($headInfoString, $bodyInfoSting) = explode($br.$br, $bodyInfoSting, 2);
        $rspData['head']            = $headInfoString;
        $rspData['body']            = $bodyInfoSting;
        $rspData['COOKIE']          = $param['COOKIE'] ?? '';
        $rspData['header_size']     = strlen($headInfoString);
        $rspData['size_upload']     = max($rspData['upload_content_length'], 0.0);
        $rspData['size_download']   = strlen($bodyInfoSting);
        if($rspData['size_download'] > 0 && $rspData['total_time'] > 0) $rspData['speed_download'] = sprintf('%.6f', $rspData['size_download']/$rspData['total_time']);
        if($rspData['size_upload'] > 0 && $rspData['total_time'] > 0) $rspData['speed_upload']     = sprintf('%.6f', $rspData['size_upload']/$rspData['total_time']);
        $headInfoList              = explode("\n", $headInfoString);
        unset($headInfoList[0]);
        if(count($headInfoList) > 0) foreach($headInfoList as $val){
            $pos            = strpos($val, ':');
            if($pos === false) continue;
            $tmpKey         = substr($val, 0, $pos);
            $tmpVal         = trim(substr($val, $pos+1));
            $tmpKeyLower    = strtolower($tmpKey);
            switch($tmpKeyLower){
                case 'content-type': {
                    $posTmp     = strpos($tmpVal, ';');
                    $rspData['content_type'] = trim($posTmp === false ? $tmpVal : substr($tmpVal, 0, $posTmp));
                    if(preg_match("/charset=([^;=\/\s]*)/i", $tmpVal, $match)) $rspData['charset'] = strtoupper($match[1]);
                    break;
                }
                case 'content-length': {
                    $rspData['download_content_length'] = intval($tmpVal);
                    break;
                }
                case 'date': {
                    $rspData['filetime'] = intval(strtotime($tmpVal));
                    break;
                }
                case 'set-cookie': {    // cookie
                    $posTmp     = strpos($tmpVal, ';');
                    if($posTmp > 0){
                        list($cookieKey, $cookieVal) = explode('=', substr($tmpVal, 0, $posTmp), 2);
                        if(strlen($cookieKey) > 0 && is_string($cookieVal)) $rspData['set_cookie'][$cookieKey] = urldecode($cookieVal);
                    }
                    break;
                }
                default: {
                    $rspData['headList'][$tmpKeyLower] = $tmpVal;
                }
            }
        }
        if(!$setInfo['IS_NOBODY'] && $rspData['download_content_length'] == -1) $rspData['download_content_length'] = $rspData['size_download'];
        if($rspData['http_code'] >= 100 && $rspData['http_code'] < 400) $rspData['result'] = true;
        return $rspData;
    }

    /**
     * @param array $postData
     * @param string $prefix
     * @return string
     * genPostDataStr
     */
    private static function genPostDataStr(array $postData, string $prefix = ''):string
    {
        $postDataString     = '';
        foreach($postData as $key => $val){
            $keyName        = strlen($prefix) > 0 ? $prefix.'['.$key.']' : $key;
            $postDataString .= (strlen($postDataString) > 0 ? '&' : '').(is_array($val) ? self::genPostDataStr($val, $keyName) : $keyName.'='.rawurlencode(is_string($val) ? $val : strval($val)));
        }
        return $postDataString;
    }
}